#include "stdafx.h"

// *****************************************************************************************
//
// NAPYR2 (http://p-nand-q.com/python/napyr.html)
//
// Copyright (c) 2014, Gerson Kurz
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright notice, this
//    list of conditions and the following disclaimer in the documentation and/or other
//    materials provided with the distribution.
// 
// 3. Neither the name of the copyright holder nor the names of its contributors may
//    be used to endorse or promote products derived from this software without
//    specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// *****************************************************************************************

#include "python.h"
#include "napyr2_Module.h"


namespace napyr2 { // begins here

typedef PyObject* (*LPFN_PyCFunction)(PyObject* self, PyObject* args);

PyObject* InvokeBase::CallbackResult(PythonObject* po)
{
	if( po == NULL )
	{
		return PythonObject::None.AddRef();
	}
	PyObject* result = po->AddRef();
	delete po;
	return result;
}

class PythonModuleMethodSlot;

PythonModuleMethodSlot* AvailableCallbackSlots[];

class PythonModuleMethodSlot
{
public:
    PythonModuleMethodSlot(LPFN_PyCFunction pCCallback)
        :	m_pInvokeBase( NULL ),
            m_pCCallback( pCCallback )
    {
    }

    void Free() // when done, remove allocation of the callback
    {
        m_pInvokeBase = NULL;
    }

    LPFN_PyCFunction Reserve(InvokeBase* instance) // when free, allocate this callback
    {
        if( m_pInvokeBase )
            return NULL;

        m_pInvokeBase = instance;
        return m_pCCallback;
    }

protected:
    static PyObject* StaticInvoke(int context, PyObject* a, PyObject* b)
	{	
		return (AvailableCallbackSlots[context]->m_pInvokeBase)->Callback(a, b);
	}

private:
    LPFN_PyCFunction m_pCCallback;
    InvokeBase* m_pInvokeBase;
};

template <int context> class DynamicPythonModuleMethodSlot : public PythonModuleMethodSlot
{
public:
    DynamicPythonModuleMethodSlot()
        :   PythonModuleMethodSlot(&DynamicPythonModuleMethodSlot<context>::GeneratedStaticFunction)
    {
    }

private:
    static PyObject* GeneratedStaticFunction(PyObject* a, PyObject* b)
    {
        return StaticInvoke(context, a, b);
    }
};

class PythonMemberFunctionCallback
{
public:
	PythonMemberFunctionCallback(InvokeBase* instance)
	{
		for( m_nAllocIndex = 0; AvailableCallbackSlots[m_nAllocIndex] != NULL; ++m_nAllocIndex )
		{
			m_cbCallback = AvailableCallbackSlots[m_nAllocIndex]->Reserve(instance);
			if( m_cbCallback != NULL )
				break;
		}
		printf("allocate slot %d\n", m_nAllocIndex);
	}

	~PythonMemberFunctionCallback()
	{
		if( IsValid() )
		{
			AvailableCallbackSlots[m_nAllocIndex]->Free();
		}
	}

public:
    operator LPFN_PyCFunction() const
    {
        return m_cbCallback;
    }

    bool IsValid() const
    {
        return m_cbCallback != NULL;
    }

private:
    LPFN_PyCFunction m_cbCallback;
    int m_nAllocIndex;

private:
    PythonMemberFunctionCallback( const PythonMemberFunctionCallback& os );
    PythonMemberFunctionCallback& operator=( const PythonMemberFunctionCallback& os );
};

#define ALLOCATE_16_MODULES(BASE) \
	new DynamicPythonModuleMethodSlot<BASE##0>(), \
	new DynamicPythonModuleMethodSlot<BASE##1>(), \
	new DynamicPythonModuleMethodSlot<BASE##2>(), \
	new DynamicPythonModuleMethodSlot<BASE##3>(), \
	new DynamicPythonModuleMethodSlot<BASE##4>(), \
	new DynamicPythonModuleMethodSlot<BASE##5>(), \
	new DynamicPythonModuleMethodSlot<BASE##6>(), \
	new DynamicPythonModuleMethodSlot<BASE##7>(), \
	new DynamicPythonModuleMethodSlot<BASE##8>(), \
	new DynamicPythonModuleMethodSlot<BASE##9>(), \
	new DynamicPythonModuleMethodSlot<BASE##A>(), \
	new DynamicPythonModuleMethodSlot<BASE##B>(), \
	new DynamicPythonModuleMethodSlot<BASE##C>(), \
	new DynamicPythonModuleMethodSlot<BASE##D>(), \
	new DynamicPythonModuleMethodSlot<BASE##E>(), \
	new DynamicPythonModuleMethodSlot<BASE##F>(), \

PythonModuleMethodSlot* AvailableCallbackSlots[] = 
{
	ALLOCATE_16_MODULES(0x00)
	ALLOCATE_16_MODULES(0x01)
	ALLOCATE_16_MODULES(0x02)
	ALLOCATE_16_MODULES(0x03)
	ALLOCATE_16_MODULES(0x04)
	ALLOCATE_16_MODULES(0x05)
	ALLOCATE_16_MODULES(0x06)
	ALLOCATE_16_MODULES(0x07)
	ALLOCATE_16_MODULES(0x08)
	ALLOCATE_16_MODULES(0x09)
	ALLOCATE_16_MODULES(0x0A)
	ALLOCATE_16_MODULES(0x0B)
	ALLOCATE_16_MODULES(0x0C)
	ALLOCATE_16_MODULES(0x0D)
	ALLOCATE_16_MODULES(0x0E)
	ALLOCATE_16_MODULES(0x0F)
	NULL
};


void PythonModule::AddFunction( LPCSTR name, InvokeBase* instance, LPCSTR docstring )
{
	PythonMemberFunctionCallback* pmfc = new PythonMemberFunctionCallback(instance);
	AddFunction(name, *pmfc, docstring);
	m_allocated_methods.push_back(pmfc);
}

PythonModule::PythonModule( LPCSTR name, LPCSTR docstring )
	:	m_name( name ),
		m_docstring( docstring ),
		m_pyModule( NULL )
{
}

PythonModule::~PythonModule()
{
}

void PythonModule::AddFunction( LPCSTR name, PyCFunction function, LPCSTR docstring )
{	
	if( docstring == NULL )
		docstring = "";

	if( *docstring )
	{
		docstring = _strdup(docstring);
	}
	else 
	{
		docstring = NULL;
	}

	PyMethodDef method;
	method.ml_name = _strdup(name);
	method.ml_doc = docstring;
	method.ml_flags = METH_VARARGS;
	method.ml_meth = function;
	m_methods.push_back(method);
}

void PythonModule::Define( LPCSTR name, const PythonObject& po )
{
	auto it = m_defines.find(name);
	if( it != m_defines.end() )
	{
		Py_DECREF(it->second);
	}
	m_defines[name] = po.AddRef();
}

} // namespace napyr2 ends here.


